#ifndef BASICVIS_CPP
#define BASICVIS_CPP
#include "basicvis.h"
#define PI 3.14159265
#include <bitset>
#include <limits>

BasicVis::BasicVis(DataBackend * c,QWidget* parent,Qt::WindowFlags f):QWidget(parent, f) {
	conn = c;
	numBars = 20;
	fps = 20; 
	theta = 0; fade = false;
	linearGrad = new QLinearGradient(QPointF(0,0), QPointF(10,180));
	linearGrad->setColorAt(0, QColor( 187,213,225,255 ));
	linearGrad->setColorAt(1, QColor("lightgray"));
	
	fullSpeaker = QImage(":images/speaker.png").scaled(150,150, Qt::IgnoreAspectRatio,Qt::SmoothTransformation);
	smallSpeaker = fullSpeaker.scaled(145,145, Qt::IgnoreAspectRatio,Qt::SmoothTransformation);

	type = SPECTRUM;

	t1 = 0;
	t2 = 0;
	t3 = 0;
	t4 = 0;
	sub = 0;

	vis_names << "Speaker"<<"Spectrum"<<"Scope"<<"Ozone"<<"Waterfall"<<"Radar";	
	menu = new QMenu();
	foreach(QString str,vis_names) {
		menu->addAction(str);
	}
	connect(menu,SIGNAL(triggered(QAction*)),this,SLOT(changeVis(QAction*)));

	//FFT data (frequencies)
	fft_conn =  conn->initVisualization();
	fft_conn->setProperty("type", "spectrum");
	fft_conn->setProperty("stereo", "0");

	//PCM raw data (amplitudes)
	pcm_conn =  conn->initVisualization();
	pcm_conn->setProperty("type", "pcm");
	pcm_conn->setProperty("stereo", "1");
	
	//Peak data (max amps)
	peak_conn = conn->initVisualization();
	peak_conn->setProperty("type", "peak");
	peak_conn->setProperty("stereo", "1");
	
	if(fft_conn->start() && pcm_conn->start() && peak_conn->start()){
		timerID = startTimer(1000/(qMax(fps,1)));
	}
	connect(conn,SIGNAL(qsettingsValueChanged(QString,QVariant)),this,SLOT(respondToConfigChange(QString,QVariant)));
	connect(conn,SIGNAL(changeStatus(Xmms::Playback::Status)),this,SLOT(beginLoop()));
}

BasicVis::~BasicVis() {
//	**Commented out, causing segfaults**
// 	delete oz_painter;
}

void BasicVis::timerEvent(QTimerEvent *event) {
	if(event->timerId()==timerID) {
		beginLoop();
	}
}

void BasicVis::beginLoop() {
	switch(type) {
		case SPEAKER:
		case SPECTRUM:
		case WATERFALL:
		case RADAR:
		visSize = fft_conn->getChunk(data_buffer,1000/(qMax(fps,1)),100);
		break;
		case OZONE:
		visSize = peak_conn->getChunk(data_buffer,1000/(qMax(fps,1)),100);
		break;
		default:
		visSize = pcm_conn->getChunk(data_buffer,1000/(qMax(fps,1)),100);
		break;
	}
	if(visSize == 0 && timerID != -1) {
		killTimer(timerID);	
		timerID = -1;
	}
	else if(visSize > 0 && timerID == -1) {
		timerID = startTimer(1000/(qMax(fps,1)));
	}
	update();
}

void BasicVis::changeVis(QAction* act) {
	for(int i=0;i<vis_names.size();i++) {
		if(vis_names[i]==act->text()) {
			changeVis(i);
		}
	}
}

void BasicVis::changeVis(int t) {
	if(t<0) t = 5;
		if(t>=6 || t ==0) {
		type = SPEAKER;
		}
		else if(t==1)
		type = SPECTRUM;
		else if(t==2)
		type = SCOPE;
		else if(t==3)
		type = OZONE;
		else if(t==4)
		type = WATERFALL;
		else
		type = RADAR;
}

void BasicVis::resizeEvent(QResizeEvent * event) {
	std::cout<<height()<<" "<<width()<<std::endl;
	if(!t1 && !t2 && !t3 && !t4 && !sub) {
	int subPos =  qMin(height(),width()-height());
	t1 = new Speaker(conn,height()/2,height()/2,11000,2);
	t2 = new Speaker(conn,height()/2,height()/2,11000,2);
	t3 = new Speaker(conn,height()/2,height()/2,11000,2);
	t4 = new Speaker(conn,height()/2,height()/2,11000,2);
		if(subPos <= 0) subPos = 1; 
		sub = new Speaker(conn,subPos,subPos,11000,5);
	}
	num_colls = width();
	screen_buf = QPixmap(width(),height());
	screen_buf.fill(qApp->palette().color(QPalette::Window));
	screen_buf_3 = QPixmap(width(),height());
	screen_buf_3.fill(qApp->palette().color(QPalette::Window));
	oz_painter = new QPainter(&screen_buf_3);


	qreal ybar = height()/2.0;
	qreal xbar = width()/2.0;
	screen_buf_2 = QPixmap(width(),height());
	screen_buf_2.fill(qApp->palette().color(QPalette::Window));
	QPainter buf_paint(&screen_buf_2);
	QColor back; back.setHsv(115,255,15);
	buf_paint.setBrush(QBrush(back));
	buf_paint.drawEllipse((int)(xbar-RADAR_RADIUS),(int)(ybar-RADAR_RADIUS),(int)(2*RADAR_RADIUS),(int)(2*RADAR_RADIUS));
	
	radar_screen = QPixmap(":images/radar_screen.png").scaled((int)(2*RADAR_RADIUS)-2,(int)(2*RADAR_RADIUS)-2,Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}


void BasicVis::respondToConfigChange(QString name,QVariant value) {
	if(name == "konfetka/visFps") {
	fps = value.toInt();
	killTimer(timerID);	
	timerID = startTimer(1000/(qMax(fps,1)));
	}
	else if(name == "konfetka/visNumBars") {
	numBars = value.toInt();
	}
	else if(name == "konfetka/visRadarFade") {
	fade = value.toBool();
	}
}

void BasicVis::paintEvent(QPaintEvent * event) {
	QPainter painter(this);
// 	std::cout<<width()<<" "<<height()<<std::endl;
	if(type == SPEAKER && visSize == 256) {
		if(t1 && t2 && t3 && t4 && sub) {
		int subPos =  qMin(height(),width()-height());
		int subX = qMax(height()/2,height()/2+(width()-height()-subPos)/2);
// 		painter.eraseRect(0,0,width(),height());
		if(visSize == 256) {		
			t1->updateSpeaker(data_buffer[20]);
			t2->updateSpeaker(data_buffer[20]);
			t3->updateSpeaker(data_buffer[40]);
			t4->updateSpeaker(data_buffer[40]);
			sub->updateSpeaker(data_buffer[2]);
		}
		painter.drawPixmap(0,0,QPixmap::grabWidget(t1));
		painter.drawPixmap(width()-height()/2,0,QPixmap::grabWidget(t2));
		painter.drawPixmap(0,height()/2,QPixmap::grabWidget(t3));
		painter.drawPixmap(width()-height()/2,height()/2,QPixmap::grabWidget(t4));
			//Sometimes the sub gets "squeezed out"
			if(subX > 5) {
			painter.drawPixmap(subX,0,QPixmap::grabWidget(sub));
			}
		}
	}
	else if(type == SPECTRUM && visSize == 256) {
		if(numBars == 0 || fps == 0) {	
			painter.eraseRect(geometry());
		return;
		}
		
		painter.setBrush(*linearGrad);
		painter.setPen(QPen(Qt::white));
		int w =qRound((qreal)width() / (qreal)numBars);
		//draw onto a pixmap 
		QPixmap p(w,height());
		QPainter pixmapPainter(&p);
		pixmapPainter.setBrush(*linearGrad);
		pixmapPainter.setPen(QPen(Qt::white));
		pixmapPainter.drawRect(0,0,w,height());
		//draw the pixmap numBars times instead of drawing numBars rectangles (cpu intensive)
		if(visSize == 256) {
			int bar = 0;
			double val = 0.0; //0.0-1.0
			for(int i =0;i<VISWATCH && bar<numBars;i+=(int)(((double)VISWATCH/(double)numBars)),bar+=1) {
				val = (((double)data_buffer[i]+(double)data_buffer[i]+(double)data_buffer[i])/(3*SHRT_MAX));
				//val = (log10(val)*40+50)/50;
				painter.drawPixmap(bar*w,(int)(height() - val*(.75*(double)(height()))),p);			
			}
		}
	}
	else if(type == SCOPE && visSize == 1024) {		
//		painter.setRenderHint(QPainter::Antialiasing); // on second thought this takes way too much cpu
		painter.setPen(QPen(QBrush(QColor("#696969")),.5));
		
		qreal mid = height()/2.0;
		int val; 
		int lastX = 0; int lastY = (int)mid;
		for(int i=0;i< qMin(width(),visSize);i+=2) {
			val = (int)(mid - ((double)data_buffer[i]/SHRT_MAX)*(mid));
			painter.drawLine(lastX,lastY,i,lastY);
			painter.drawLine(i,lastY,i,val);	
			lastX = i; lastY = val;
		}
	}
	else if (type == OZONE && visSize == 2) {
		//Jitu your code goes here
		screen_buf_3.fill(qApp->palette().color(QPalette::Window));
		//drawLightning(QColor(255, 0,0), 100, 0, height()/2, 0, (double)data_buffer[0]/(double)SHRT_MAX, 1);
		//drawLightning(QColor(0, 0, 255), 150, width(), height()/2, 3.14, (double)data_buffer[1]/(double)SHRT_MAX, 1);
		drawLightning(QColor(255, 0,0), 50, width()/2, height()/2, 3.14, pow((double)data_buffer[0]/(double)SHRT_MAX, 0.7), 1);
		drawLightning(QColor(0, 0, 255), 50, width()/2, height()/2, 0.0, pow((double)data_buffer[1]/(double)SHRT_MAX, 0.7), 1);
		painter.drawPixmap(0,0,screen_buf_3);
		//For Jitu
	}
	else if(type == WATERFALL && visSize ==256) {
		qreal mid = height()/2.0+(WATERFALL_HEIGHT/2);
		QPainter buf_paint(&screen_buf);		
		QColor c = Qt::white;
		buf_paint.drawPixmap(-1,0,screen_buf);
		qreal x = 0;
		qreal y = 0;
		int cntr = 0;
		for(int j=0;j<WATERFALL_HEIGHT && cntr<256;j++,cntr+=2) {
			cntr==0?x=data_buffer[cntr]:x=data_buffer[cntr-1];
			x+=data_buffer[cntr];
			cntr<255?x+=data_buffer[cntr+1]:x+=data_buffer[cntr];
			x/=3.0;x/=(qreal)SHRT_MAX;
			//x = (qreal)data_buffer[cntr] / (qreal)SHRT_MAX;
			if(x < .1)
                        c = Qt::white;
			else if (x < .25)
			c.setHsv(95, 255, 255 - int(x * 255.0));
			else if (x < .7)
			c.setHsv(95 - int(x * 90.0), 255, 255);
			else
                        c = Qt::red;

			buf_paint.setPen(c);
			buf_paint.drawPoint(width()-1,(int)mid-j);
		}
		painter.drawPixmap(0,0,screen_buf);		
	}
	else if(type == RADAR && visSize == 256) {
		qreal ybar = height()/2.0;
		qreal xbar = width()/2.0;
		theta++;
		theta = theta % 360;
		QPainter buf_paint(&screen_buf_2);
		//buf_paint.setRenderHint(QPainter::Antialiasing);
		int cntr = 0; QColor c; qreal x = 0;
		for(int j=0;j<RADAR_RADIUS && cntr<256;j++,cntr+=2) {
			if(j>20) 
				x = (qreal)data_buffer[cntr] / (qreal)SHRT_MAX;
			else 
				x = (qreal)data_buffer[j] / (qreal)SHRT_MAX;
			if(x < .05)
                        c.setHsv(115,255,15);
			else if (x < .25)
			c.setHsv(115, 255, 30 + int(x * 100.0));
			else if (x < .7)
			c.setHsv(115 - int(x * 50.0), 255, 80);
			else
                        c.setHsv(115,255,150);

			buf_paint.setPen(c);
			buf_paint.drawPoint((int)(xbar+j*cos(.01745329*theta)),(int)(ybar+j*sin(.01745329*theta)));
		}
		
		painter.drawPixmap(0,0,screen_buf_2);
		if(fade) {
			painter.translate((int)(xbar),(int)(ybar));
			painter.rotate(theta);
			painter.drawPixmap(-1*RADAR_RADIUS,-1*RADAR_RADIUS,radar_screen);
			painter.resetMatrix();
		}
		painter.setPen(QColor::fromHsv(115,255,255));
		painter.drawLine((int)xbar,(int)ybar,(int)(xbar+RADAR_RADIUS*cos(.01745329*theta)),
				(int)(ybar+RADAR_RADIUS*sin(.01745329*theta)));
	}
}

void BasicVis::setNumAndLen(int num,int len) {fps = len; numBars = num;}

void BasicVis::mousePressEvent(QMouseEvent * event) {
	if(event->button() == Qt::LeftButton) {
		if(timerID>0) {
			killTimer(timerID);
			timerID = -1;		
		}
		else
		timerID = startTimer(1000/(qMax(fps,1)));;
	}
	else if(event->button() == Qt::RightButton) {
		menu->popup(event->globalPos());	
	}
	update();
}

void BasicVis::wheelEvent(QWheelEvent * event){
	int t;
	if(event->delta()>0) t = type + 1;
	if(event->delta()<0) t = type - 1;
	changeVis(t);
}

void BasicVis::drawLightning(QColor clr, double freq, double x, double y, double dir, double pwr, double fullpwr) {
	double nx, ny;
	bool leave = false;
	
// ozoneStart: // NO jitu we don't use gotos
	do {
		double dist = 5*((double)rand()/(double)(RAND_MAX))*pwr;
		double ddir = ((double)rand()/(double)(RAND_MAX))*(3.14)/4*qMin(1, 10/(int)pow(freq, 0.4));
		if (((double)rand()/(double)(RAND_MAX)) < 0.5) ddir *= -1;
		nx = x+cos(dir+ddir)*dist;
		ny = y+sin(dir+ddir)*dist;
			//double shade = pwr/fullpwr*0.6+0.4;
		oz_painter->setPen(clr);
		oz_painter->drawLine((int)x, (int)y, (int)nx, (int)ny);
		
		//repaint();
		//try {Thread.sleep(1);}
		//catch (InterruptedException e) {}
		leave = false;
		if (pwr > 0.2) {
			if (((double)rand()/(double)(RAND_MAX))*((1-pwr/fullpwr)*0.02+0.98) > 0.9) {
				double a = ((double)rand()/(double)(RAND_MAX))*(3.14)/12*(((double)rand()/(double)(RAND_MAX))<0.5?1:-1)+((double)rand()/(double)(RAND_MAX))*(3.14)/6;
				a *= (((double)rand()/(double)(RAND_MAX))<0.5?1:-1);
				drawLightning(clr, freq, nx, ny, dir+a, pwr*(((double)rand()/(double)(RAND_MAX))*0.5+0.25), fullpwr);
				dir -= a;
			}
			pwr *= 0.99;
			x = nx;
			y = ny;
			leave = true;
		}
	} while(leave);
}

/***********************************- Speaker- *****************************************/ 
Speaker::Speaker(DataBackend * c,int w, int h,int freq,int delta,QWidget * parent, Qt::WindowFlags f ):QWidget(parent, f){
	conn = c;
	frequency = freq;
	value = 0;
	fullSpeaker.load(":images/speaker.png");
	fullSpeaker = fullSpeaker.scaled(w,h, Qt::IgnoreAspectRatio,Qt::SmoothTransformation);
	smallSpeaker = fullSpeaker.scaled(w-delta,h-delta, Qt::IgnoreAspectRatio,Qt::SmoothTransformation);
	resize(w,h);
	this->delta = delta;
	draw = true; beat = false;
}

void Speaker::paintEvent(QPaintEvent * ev) {
	QPainter painter(this);
	painter.eraseRect(0,0,width(),height());
	painter.drawPixmap(0,0,fullSpeaker);
	if(draw) {
//		qDebug()<<value<<lastvalue<<value-lastvalue;
		if(beat) {
		beat = false;
		}		
		else if(value>(SHRT_MAX/2)) {
			painter.drawPixmap(delta/2,delta/2,smallSpeaker);
			beat = true;
		}
	draw = false;
	}
}

void Speaker::updateSpeaker(int newValue) {
	lastvalue =  value;
	value = newValue;
	draw = true;
	update();
}

/***************************************************************************************/

#endif

